package com.wjh.www.config;

import com.wjh.www.annotation.spring.Autowired;
import com.wjh.www.annotation.spring.Component;
import com.wjh.www.annotation.spring.Value;
import com.wjh.www.proxy.MyProxy;
import com.wjh.www.util.LoggerUtil;

import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLDecoder;
import java.util.*;
import java.util.logging.Logger;

/**
 * 实现ioc容器的配置类
 *
 * @author wjh
 * @date 2021/5/26 0:05
 * @Package com.wjh.www.util
 */
public class IocContext {
    private final static Map<String, Object> IOC_MAP = new HashMap<>();
    private final static List<String> BEAN_NAMES = new ArrayList<>();
    private static final Properties PROPERTIES = new Properties();
    private static final Logger logger = LoggerUtil.getLogger();

    static {
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        InputStream is = cl.getResourceAsStream("ioc.properties");
        try {
            PROPERTIES.load(is);
        } catch (IOException e) {
            e.printStackTrace();
        }
        //扫描的包路径（这里默认本项目所以的包）
        String packageName = PROPERTIES.getProperty("ioc.scan.path");
        //初始化ioc容器
        init(packageName);
    }

    private IocContext() {

    }

    /**
     * 初始化ior容器并进行自动装配
     *
     * @param packName 被扫描的包名
     */
    private static void init(String packName) {
        // 遍历包，找到需要交给ioc管理的目标类
        Set<BeanDefinition> beanDefinitions = initBeanDefinitions(packName);
        //根据原材料创建bean
        createBean(beanDefinitions);
        //自动装载
        autowireBean(beanDefinitions);
        logger.info("ioc容器初始化成功");
    }

    /**
     * 对外提供获取bean的访问方法
     *
     * @param beanName IOC_MAP的key
     * @param <T>      返回的类型
     * @return 返回IOC_MAP的value
     */
    public static <T> T getBean(String beanName) {
        return (T) IOC_MAP.get(beanName);
    }

    /**
     * 扫描包路径，获取需要交给ioc管理的类
     *
     * @param packageName 被扫描的包名
     * @return 返回Set<BeanDefinition>
     */
    private static Set<BeanDefinition> initBeanDefinitions(String packageName) {
        //获取包下的所有类
        Set<Class<?>> classes = getAllClasses(packageName);
        //迭代classes
        Iterator<Class<?>> iterator = classes.iterator();
        //定义存放beanName->class的集合，使用set集合目的是为了去重
        Set<BeanDefinition> beanDefinitions = new HashSet<>();
        //遍历classes，找到有标注@Component的类
        while (iterator.hasNext()) {
            Class<?> clazz = iterator.next();
            if (clazz.isAnnotationPresent(Component.class)) {
                //获取Component注解的值
                String beanName = clazz.getAnnotation(Component.class).value();

                if ("".equals(beanName)) {
                    //如果注解上面没有value，则获取类名首字母小写
                    String className = clazz.getName().replaceAll(clazz.getPackage().getName() + ".", "");
                    beanName = className.substring(0, 1).toLowerCase() + className.substring(1);
                }
                //将这些类封装成BeanDefinition，装进set集合中
                beanDefinitions.add(new BeanDefinition(beanName, clazz));
                BEAN_NAMES.add(beanName);
                logger.info("ioc上下文中包含的对象的beanName：" + beanName);
            }
        }
        return beanDefinitions;
    }

    /**
     * 获取需要交给ioc管理的类的class
     *
     * @param packageName 包名
     * @return 返回Set<Class < ?>>
     */
    private static Set<Class<?>> getAllClasses(String packageName) {
        // 存放classes类的集合，LinkedHashSet具有set集合不重复的特点，同时具有可预测的迭代顺序（插入的顺序）
        Set<Class<?>> classes = new LinkedHashSet<>();
        // 是否循环迭代
        boolean recursive = true;
        // 获取包的名字 并进行替换
        String packageDirName = packageName.replace('.', '/');
        // 定义一个枚举的集合 并进行循环来处理这个目录下的things
        Enumeration<URL> dirs;
        try {
            dirs = Thread.currentThread().getContextClassLoader().getResources(packageDirName);
            // 循环迭代下去
            while (dirs.hasMoreElements()) {
                // 获取下一个元素
                URL url = dirs.nextElement();
                // 得到协议的名称
                String protocol = url.getProtocol();
                // 如果是以文件的形式保存在服务器上
                if ("file".equals(protocol)) {
                    // 获取包的物理路径
                    String filePath = URLDecoder.decode(url.getFile(), "UTF-8");
                    // 以文件的方式扫描整个包下的文件 并添加到集合中
                    findClassesInPackageByFile(packageName, filePath, recursive, classes);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return classes;
    }

    /**
     * 扫描file类型的文件（其实本ioc也只支持file的扫描）
     *
     * @param packageName 被扫描的包名
     * @param packagePath 包的路径
     * @param recursive
     * @param classes     ioc管理的类的class
     */
    private static void findClassesInPackageByFile(String packageName, String packagePath, final boolean recursive, Set<Class<?>> classes) {
        // 获取此包的目录，建立一个File
        File dir = new File(packagePath);
        // 如果不存在或者也不是目录就直接返回
        if (!dir.exists() || !dir.isDirectory()) {
            return;
        }
        // 如果存在就获取包下的所有文件，包括目录
        File[] dirFiles = dir.listFiles(new FileFilter() {
            // 自定义过滤规则，如果可以循环(包含子目录) 或则是以.class结尾的文件(编译好的java类文件)
            @Override
            public boolean accept(File file) {
                return (recursive && file.isDirectory()) || (file.getName().endsWith(".class"));
            }
        });
        // 循环所有文件
        for (File file : dirFiles) {
            // 如果是目录 则继续扫描
            if (file.isDirectory()) {
                findClassesInPackageByFile(packageName + "." + file.getName(), file.getAbsolutePath(), recursive, classes);
            } else {
                // 如果是java类文件 去掉后面的.class 只留下类名
                String className = file.getName().substring(0, file.getName().length() - 6);
                try {
                    // 添加到集合中去
                    classes.add(Thread.currentThread().getContextClassLoader().loadClass(packageName + '.' + className));
                } catch (ClassNotFoundException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 创建ioc管理的对象
     *
     * @param beanDefinitions 存放beanName->beanClass的set集合
     */
    private static void createBean(Set<BeanDefinition> beanDefinitions) {
        //遍历set集合
        for (BeanDefinition beanDefinition : beanDefinitions) {
            String beanName = beanDefinition.getBeanName();
            Class<?> clazz = beanDefinition.getBeanClass();
            try {
                //只有当该类不是一个接口，才能newInstance
                if (!clazz.isInterface()) {
                    //创建的对象
                    Object target = clazz.getConstructor().newInstance();

                    //完成属性的赋值（如果没有标有value注解的话就不需要赋值）
                    Field[] declaredFields = clazz.getDeclaredFields();
                    for (Field declaredField : declaredFields) {
                        Value valueAnnotation = declaredField.getAnnotation(Value.class);
                        if (valueAnnotation != null) {
                            String value = valueAnnotation.value();
                            String fieldName = declaredField.getName();
                            String methodName = "set" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
                            Method method = clazz.getMethod(methodName, declaredField.getType());
                            Object val;
                            //获取该属性的String类型的构造器
                            Constructor<?> constructor = declaredField.getType().getConstructor(String.class);
                            val = constructor.newInstance(value);
                            //反射setXxx
                            method.invoke(target, val);
                        }
                    }
                    //存入ioc容器
                    IOC_MAP.put(beanName, target);
                } else {
                    //否则创建该类的代理对象
                    IOC_MAP.put(beanName, MyProxy.getInstance().getProxyInstance(clazz));
                }

            } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
                e.printStackTrace();
            }
        }
    }


    /**
     * 实现自动装配
     *
     * @param beanDefinitions 存放beanName->beanClass的set集合
     */
    private static void autowireBean(Set<BeanDefinition> beanDefinitions) {
        for (BeanDefinition beanDefinition : beanDefinitions) {
            //当前正在被装配的实例对象的beanName
            String targetBeanName = beanDefinition.getBeanName();
            //当前被装配对象的class
            Class<?> clazz = beanDefinition.getBeanClass();
            //获取当前正在被装配的对象的属性
            Field[] declaredFields = clazz.getDeclaredFields();
            for (Field declaredField : declaredFields) {
                declaredField.setAccessible(true);
                Autowired autowiredAnnotation = declaredField.getAnnotation(Autowired.class);
                //如果标有@Autowired注解则进行装配
                if (autowiredAnnotation != null) {
                    String autowiredValue = autowiredAnnotation.value();
                    Object bean;
                    if (!"".equals(autowiredValue)) {
                        bean = getBean(autowiredValue);
                        try {
                            //从ioc容器中获取对应的对象
                            declaredField.set(getBean(targetBeanName), bean);
                        } catch (IllegalAccessException e) {
                            e.printStackTrace();
                        }
                    } else {
                        String beanName = declaredField.getType().getSimpleName();
                        try {
                            //给当前正在被装载的对象的当前属性注入ioc上下文中存在的对象
                            declaredField.set(getBean(targetBeanName), getBean(beanName.substring(0, 1).toLowerCase() + beanName.substring(1)));
                        } catch (IllegalAccessException e) {
                            e.printStackTrace();
                        }
                    }
                }

            }
        }
    }

    /**
     * 存放beanName->beanClass的类
     */
    private static class BeanDefinition {
        private String beanName;
        private Class<?> beanClass;

        public String getBeanName() {
            return beanName;
        }

        public void setBeanName(String beanName) {
            this.beanName = beanName;
        }

        public Class<?> getBeanClass() {
            return beanClass;
        }

        public void setBeanClass(Class<?> beanClass) {
            this.beanClass = beanClass;
        }

        public BeanDefinition(String beanName, Class<?> beanClass) {
            this.beanName = beanName;
            this.beanClass = beanClass;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) {
                return true;
            }
            if (o == null || getClass() != o.getClass()) {
                return false;
            }
            BeanDefinition that = (BeanDefinition) o;
            return Objects.equals(beanName, that.beanName) && Objects.equals(beanClass, that.beanClass);
        }

        @Override
        public int hashCode() {
            return Objects.hash(beanName, beanClass);
        }
    }
}
